/**

This file is part of MaCI/GIMnet.

MaCI/GIMnet is free software: you can redistribute it and/or modify it 
under the terms of the GNU Lesser General Public License as published 
by the Free Software Foundation, either version 3 of the License, or 
(at your option) any later version.

MaCI/GIMnet is distributed in the hope that it will be useful, but WITHOUT 
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 
FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public 
License for more details.

You should have received a copy of the GNU Lesser General Public 
License along with GIMnet. (See COPYING.LESSER) If not, see 
<http://www.gnu.org/licenses/>.

**/
/**
 *
 * \file
 * \brief Process executor class.
 * \author Antti Maula <antti.maula@tkk.fi>
 */

#include "executor.hpp"
#include "ownutils.h"
#include "owndebug.h"
#include <assert.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <poll.h>

//*****************************************************************************
//*****************************************************************************
//*****************************************************************************

CExecutor::CExecutor()
  : iProcessID(-1),
    iInputHandle(-1),
    iOutputHandle(-1),
    iErrorHandle(-1),
    iPipeRead(-1),
    iPipeWrite(-1),
    iCommand(),
    iParameters(),
    iDelay_ms(0),
    iWorkDir(),
    iSetUID(false),
    iSetGID(false)
{
  dPrint(4,"Called");
}
//*****************************************************************************

CExecutor::~CExecutor()
{
  // Terminate and Wait
  Kill();
  Wait();

  if (iPipeWrite >= 0) close(iPipeWrite);
  if (iPipeRead >= 0) close(iPipeRead);
}
//*****************************************************************************

bool CExecutor::CreateIOPipe()
{
  bool result = false;
  if (iProcessID < 0) {
    
    int fdes_in[2];
    int fdes_out[2];
    
    int r = pipe(fdes_in);
    r += pipe(fdes_out);
    if (r != 0) {
      dPrintLE(2,"Failed to create IO Pipe!");
      
    } else {
      dPrint(4,"Succesfully created IO pipe. (read: %d, write: %d)",
             fdes_in[0], fdes_out[1]);
      iPipeRead = fdes_in[0];
      iPipeWrite = fdes_out[1];
      
      SetInputHandle(fdes_out[0]);
      SetOutputHandle(fdes_in[1]);
      SetErrorHandle(fdes_in[1]);
      result = true;
    }
  } else {
    dPrint(1, "Process already running - will not create a Pipe");

  }
  return result;
}
//*****************************************************************************

bool CExecutor::Execute(const std::string &aCommand,
                        const TStringArray &aParameters,
                        const unsigned int aDelay_ms,
                        const std::string &aWorkDir,
                        const int aSetUID,
                        const int aSetGID)
{
  bool result = false;

  // Attempt to execute!
  if (iProcessID < 0) {
    // Check whether we have 'Execute' access to the file.
    int r = 0;
    char cwdir[256];
    if (getcwd(cwdir, sizeof(cwdir)) != NULL) {
      if (aWorkDir.size()) r = chdir(aWorkDir.c_str());
      if (r == 0) {
        r = access(aCommand.c_str(), X_OK);
        const int chdir_back_to_home = chdir(cwdir);
        assert(chdir_back_to_home == 0); // This must succeed, otherwise we are screwed :)
      }
    }
    
    if (r != 0) {
      dPrintLE(1,"Failed to execute binary '%s' (In workdir '%s')", 
               aCommand.c_str(),
               aWorkDir.size()?aWorkDir.c_str():cwdir);
      
    } else {
      
      const int pid = fork();
      if (pid == -1) {
        dPrintLE(2, "fork() failed!");
        
      } else if (pid == 0) {
        //// This is the child :)

        dPrint(4,"Child executing...");

        // Check UID/GID settings and act as necessary
        if (aSetGID != -1) {
          dPrint(4,"Setting Child %d GID to '%d'", 
                 getpid(), aSetGID);
          int r = setgid(aSetGID);
          if (r != 0) {
            dPrintLE(2,"Child %d Failed to change to GID %d!", 
                     getpid(), aSetGID);
          }
        }
        if (aSetUID != -1) {
          dPrint(4,"Setting Child %d UID to '%d'", 
                 getpid(),aSetUID);
          int r = setuid(aSetUID);
          if (r != 0) {
            dPrintLE(2,"Child %d Failed to change to UID %d!", 
                     getpid(), aSetUID);
          }
        }



        // Already in child, so we can change working directory.
        if (aWorkDir.size()) {
          // Special working directory is set. So change!
          int v = chdir(aWorkDir.c_str());
          if (v != 0) {
            dPrintLE(2,"Failed to change working directory to '%s'! Terminating.",
                     aWorkDir.c_str());
          
            // Terminate child.
            exit(1);

          } else {
            dPrint(3,"Succesfully changed working directory to '%s'",
                   aWorkDir.c_str());
          }
        }

        if (aDelay_ms > 0) {
          dPrint(2,"Process execution delayed for %ums", aDelay_ms);
          // Sleep
          ownSleep_ms(aDelay_ms);

          dPrint(2,"Process execution continues.");
        }

        // Redirect IO
        RedirectIO(iInputHandle, iOutputHandle, iErrorHandle);

        // Create ptr array of parameters
        char **a = CreatePtrArray(aCommand, aParameters);

        // Execute the process!
        execv(aCommand.c_str(), a);
        dPrintLE(2, "execv('%s') failed!", aCommand.c_str());
        exit(1);
      
      } else {
        // This is the parent :)
        dPrint(2,"Succesfully executed '%s', got PID %d",
               aCommand.c_str(), pid);


        // store parameters for later call to Reexecute()
        iCommand = aCommand;
        iParameters = aParameters;
        iDelay_ms = aDelay_ms;
        iWorkDir = aWorkDir;
        iSetUID = aSetUID;
        iSetGID = aSetGID;
        
        // Parent now closes the redirected handles if pipe exists.
        //if (iPipeRead >= 0 && iOutputHandle >= 0) close(iOutputHandle);
        //if (iPipeWrite >= 0 && iInputHandle >= 0) close(iInputHandle);
      
        iProcessID = pid;
        result = true;
      }
    }
  } else {
    dPrint(2,"Will not Execute, process already running with PID %d", 
           iProcessID);
  }
    
  return result;
}
//*****************************************************************************

bool CExecutor::ReExecute(const unsigned int aDelay_ms_add)
{
  bool result = Execute(iCommand, iParameters, iDelay_ms + aDelay_ms_add, 
                        iWorkDir, iSetUID, iSetGID);

  // Overwrite the time, cause its now too much.
  iDelay_ms -= aDelay_ms_add;

  return result;
}
//*****************************************************************************

int CExecutor::Wait(int &aResult, bool aBlock)
{
  int r = -1;
  const int options = (aBlock) ? 0 : WNOHANG;

  if (iProcessID > 0) {
    r = waitpid(iProcessID, &aResult, options);
    if (r < 0) {
      dPrintLE(3,"Failed to Wait() for PID %d (or status not available)", 
               iProcessID);
      
    } else if (r == iProcessID) {
      dPrint(3,"Succesfully Wait():ed for PID %d, got result '%d'", 
             iProcessID, aResult);
      iProcessID = -1;

    }
  }

  // return the 'r' unmodified
  return r;
}
//*****************************************************************************

bool CExecutor::Kill()
{
  bool result = false;
  if (iProcessID > 0) {
    int r = kill(iProcessID, SIGKILL);
    if (r != 0) {
      dPrintLE(2,"Killing PID %d failed!", iProcessID);
    
    } else {
      dPrint(2,"Succesfully killed PID %d", iProcessID);

      // After KILL we can mark the processID empty.
      result = true;

    }
  } 
  return result;
}
//*****************************************************************************

bool CExecutor::Terminate()
{
  bool result = false;
  if (iProcessID > 0) {
    int r = kill(iProcessID, SIGTERM);
    if (r != 0) {
      dPrintLE(2,"Terminate (kill(SIGTERM)) failed!");
  
    } else {
      dPrint(2,"Succesfully terminated PID %d", iProcessID);
      result = true;
    
    }
  }
  return result;
}
//*****************************************************************************

int CExecutor::ReadIOPipe(void *aBuffer, size_t aSize)
{
  int result = -1;
  if (iPipeRead >= 0) {
    result = read(iPipeRead, aBuffer, aSize);
  }
  return result;
}
//*****************************************************************************

int CExecutor::WriteIOPipe(const void *aBuffer, size_t aSize)
{
  int result = -1;
  if (iPipeWrite >= 0) {
    result = write(iPipeWrite, aBuffer, aSize);
  }
  return result;
}
//*****************************************************************************

int CExecutor::WaitReadIOPipe(const int aTimeout_ms)
{
  int result = -1;
  if (iPipeRead >= 0) {
    struct pollfd pfds[1] = {{ iPipeRead, POLLIN, 0 }};
    result = poll(pfds, 1, aTimeout_ms);
    if (result < 0) {
      dPrintLE(3,"poll() failed for 'read' file descriptor (%d)",
               iPipeRead);
    
    }
  }
  return result;
}
//*****************************************************************************

int CExecutor::WaitWriteIOPipe(const int aTimeout_ms)
{
  int result = -1;
  if (iPipeWrite >= 0) {
    struct pollfd pfds[1] = {{ iPipeWrite, POLLOUT, 0 }};
    result = poll(pfds, 1, aTimeout_ms);
    if (result < 0) {
      dPrintLE(3,"poll() failed for 'write' file descriptor (%d)",
               iPipeWrite);
      
    }
  }
  return result;
}
//*****************************************************************************

bool CExecutor::Detach(void)
{
  bool result = false;

  // Is the process active?
  if (iProcessID != -1) {

    // Just mark it inactive.
    iProcessID = -1;
    

    // Close write pipe if it exists
    if (iPipeWrite >= 0) {
      close(iPipeWrite);
      iPipeWrite = -1;
    }


    // Close read pipe if it exsits
    if (iPipeRead >= 0)  {
      close(iPipeRead);
      iPipeRead = -1;
    }
 
    // Mark operation succesfull.
    result = true;
  }
  
  
  return result;
}
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************

void CExecutor::RedirectIO(const int aNewIn,
                           const int aNewOut,
                           const int aNewErr)
{
  // Redirect standard input
  if (aNewIn >= 0) {
    close(STDIN_FILENO);
    dup2(aNewIn, STDIN_FILENO);
  }
  
  // Redirect standard output
  if (aNewOut >= 0) {
    close(STDOUT_FILENO);
    dup2(aNewOut, STDOUT_FILENO);
  }
  
  // Redirect standard error
  if (aNewErr >= 0) {
    close(STDERR_FILENO);
    dup2(aNewErr, STDERR_FILENO);
  }
}
//*****************************************************************************

char **CExecutor::CreatePtrArray(const std::string &aCommand, 
                                 const TStringArray &aArray)
{
  // Allocate memory.
  char **array = new char* [ aArray.size() + 2 ];

  // Copy the 'path' as first argument (required)
  array[0] = new char [ aCommand.size() + 1 ];
  strcpy(array[0], aCommand.c_str());

  // Start from index = 1
  int index = 1;

  // Copy strings in place.
  for(EACH_IN_i(aArray)) {
    array[index] = new char [ i->size() + 1 ];
    assert(array[index] != NULL);
    strcpy(array[index], i->c_str());
    index++;
  }
  
  // Store NULL as last. (required)
  array[index] = NULL;

  return array;
}
//*****************************************************************************

void CExecutor::DestroyPtrArray(char **aArray, const int aElements)
{
  // Delete all 'char *' 
  for(int i=0; i < aElements; ++i) {
    delete[] aArray[i];
  }

  // Delete the containing 'char **'
  delete[] aArray;
}
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
